/*!re2c
    re2c:case-insensitive    = 1;

    re2c:sentinel            = 255;
    re2c:define:YYCTYPE      = u8;
    re2c:define:YYPEEK       = "if cursor < len { *s.as_bytes().get_unchecked(cursor) } else { 255 }";
    re2c:define:YYSKIP       = "cursor += 1;";
    re2c:define:YYBACKUP     = "marker = cursor;";
    re2c:define:YYRESTORE    = "cursor = marker;";
    re2c:define:YYBACKUPCTX  = "ctxmarker = cursor;";
    re2c:define:YYRESTORECTX = "cursor = ctxmarker;";
    re2c:yyfill:enable       = 0;
    re2c:indent:string       = '    ';
    re2c:indent:top          = 1;

    wordchar = [^\x01-\x20\xff];

    spacechar = [ \t\v\f\r\n];

    reg_char     = [^\\()\x01-\x20\xff];

    escaped_char = [\\][!"#$%&'()*+,./:;<=>?@[\\\]^_`{|}~-];

    tagname = [A-Za-z][A-Za-z0-9-]*;

    blocktagname = 'address'|'article'|'aside'|'base'|'basefont'|'blockquote'|'body'|'caption'|'center'|'col'|'colgroup'|'dd'|'details'|'dialog'|'dir'|'div'|'dl'|'dt'|'fieldset'|'figcaption'|'figure'|'footer'|'form'|'frame'|'frameset'|'h1'|'h2'|'h3'|'h4'|'h5'|'h6'|'head'|'header'|'hr'|'html'|'iframe'|'legend'|'li'|'link'|'main'|'menu'|'menuitem'|'nav'|'noframes'|'ol'|'optgroup'|'option'|'p'|'param'|'search'|'section'|'title'|'summary'|'table'|'tbody'|'td'|'tfoot'|'th'|'thead'|'title'|'tr'|'track'|'ul';

    attributename = [a-zA-Z_:][a-zA-Z0-9:._-]*;

    unquotedvalue = [^ \t\r\n\v\f"'=<>`\xff]+;
    singlequotedvalue = ['][^'\xff]*['];
    doublequotedvalue = ["][^"\xff]*["];

    attributevalue = unquotedvalue | singlequotedvalue | doublequotedvalue;

    attributevaluespec = spacechar* [=] spacechar* attributevalue;

    attribute = spacechar+ attributename attributevaluespec?;

    opentag = tagname attribute* spacechar* [/]? [>];
    closetag = [/] tagname spacechar* [>];

    htmlcomment = "--" ([^\xff-]+ | "-" [^\xff-] | "--" [^\xff>])* "-->";

    processinginstruction = ([^?>\xff]+ | [?][^>\xff] | [>])+;

    declaration = [A-Z]+ spacechar+ [^>\xff]*;

    cdata = "CDATA[" ([^\]\xff]+ | "]" [^\]\xff] | "]]" [^>\xff])*;

    htmltag = opentag | closetag;

    in_parens_nosp   = [(] (reg_char|escaped_char|[\\])* [)];

    in_double_quotes = ["] (escaped_char|[^"\xff])* ["];
    in_single_quotes = ['] (escaped_char|[^'\xff])* ['];
    in_parens        = [(] (escaped_char|[^)\xff])* [)];

    scheme           = [A-Za-z][A-Za-z0-9.+-]{1,31};

    /* Phoenix Tags */
    /* https://github.com/phoenixframework/tree-sitter-heex/blob/6603380caf806b3e6c7f0bf61627bb47023d79f1/grammar.js */
    phoenix_function_component = [.][a-z] [^-<>{}!"'/= \t\r\n\v\f.\xff]*;
    phoenix_module_component = [A-Z] [^-<>{}!"'/= \t\r\n\v\f.\xff]* ([.][A-Z] [^-<>{}!"'/= \t\r\n\v\f.\xff]*)* ([.][a-z] [^-<>{}!"'/= \t\r\n\v\f.\xff]*)?;
    phoenix_slot = [:][a-z]+ [^<>{}!"'/= \t\r\n\v\f\xff]*;
    phoenix_tag = phoenix_function_component | phoenix_module_component | phoenix_slot;
    phoenix_block_tag = phoenix_function_component | phoenix_module_component;
*/

pub fn atx_heading_start(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    [#]{1,6} ([ \t]+|[\r\n\xff])  {
        if cursor == len + 1 {
            cursor -= 1;
        }
        return Some(cursor);
    }
    * { return None; }
*/
}

pub fn atx_subtext_start(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    [-][#] ([ \t]+|[\r\n\xff])  {
        if cursor == len + 1 {
            cursor -= 1;
        }
        return Some(cursor);
    }
    * { return None; }
*/
}

pub fn html_block_end_1(s: &str) -> bool {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    [^\n\xff]* [<] [/] ('script'|'pre'|'textarea'|'style') [>] { return true; }
    * { return false; }
*/
}

pub fn html_block_end_2(s: &str) -> bool {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    [^\n\xff]* '-->' { return true; }
    * { return false; }
*/
}

pub fn html_block_end_3(s: &str) -> bool {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    [^\n\xff]* '?>' { return true; }
    * { return false; }
*/
}

pub fn html_block_end_4(s: &str) -> bool {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    [^\n\xff]* '>' { return true; }
    * { return false; }
*/
}

pub fn html_block_end_5(s: &str) -> bool {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    [^\n\xff]* ']]>' { return true; }
    * { return false; }
*/
}

pub fn alert_start(s: &str) -> Option<crate::nodes::AlertType> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();

/*!re2c
    [>]{1,} ' [!note]' { return Some(crate::nodes::AlertType::Note); }
    [>]{1,} ' [!tip]' { return Some(crate::nodes::AlertType::Tip); }
    [>]{1,} ' [!important]' { return Some(crate::nodes::AlertType::Important); }
    [>]{1,} ' [!warning]' { return Some(crate::nodes::AlertType::Warning); }
    [>]{1,} ' [!caution]' { return Some(crate::nodes::AlertType::Caution); }
    * { return None; }
*/
}

pub fn open_code_fence(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let mut ctxmarker = 0;
    let len = s.len();
/*!re2c
    [`]{3,} / [^`\r\n\xff]*[\r\n\xff] { return Some(cursor); }
    [~]{3,} / [^\r\n\xff]*[\r\n\xff] { return Some(cursor); }
    * { return None; }
*/
}

pub fn close_code_fence(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let mut ctxmarker = 0;
    let len = s.len();
/*!re2c
    [`]{3,} / [ \t]*[\r\n\xff] { return Some(cursor); }
    [~]{3,} / [ \t]*[\r\n\xff] { return Some(cursor); }
    * { return None; }
*/
}

pub fn html_block_start(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    [<] ('script'|'pre'|'textarea'|'style') (spacechar | [>]) { return Some(1); }
    '<!--' { return Some(2); }
    '<?' { return Some(3); }
    '<!' [A-Za-z] { return Some(4); }
    '<![CDATA[' { return Some(5); }
    [<] [/]? blocktagname (spacechar | [/]? [>])  { return Some(6); }
    * { return None; }
*/
}

pub fn html_block_start_7(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    [<] (opentag | closetag) [\t\n\f ]* [\r\n\xff] { return Some(7); }
    * { return None; }
*/
}

pub enum SetextChar {
    Equals,
    Hyphen,
}

pub fn setext_heading_line(s: &str) -> Option<SetextChar> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    [=]+ [ \t]* [\r\n\xff] { return Some(SetextChar::Equals); }
    [-]+ [ \t]* [\r\n\xff] { return Some(SetextChar::Hyphen); }
    * { return None; }
*/
}

pub fn footnote_definition(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    '[^' ([^\] \r\n\xff\t]+) ']:' [ \t]* { return Some(cursor); }
    * { return None; }
*/
}

pub fn scheme(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    scheme [:] { return Some(cursor); }
    * { return None; }
*/
}

pub fn autolink_uri(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    scheme [:][^\x01-\x20\xff<>]*[>]  { return Some(cursor); }
    * { return None; }
*/
}

pub fn autolink_email(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    [a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+
        [@]
        [a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?
        ([.][a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*
        [>] { return Some(cursor); }
    * { return None; }
*/
}

pub fn html_tag(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    htmltag { return Some(cursor); }
    * { return None; }
*/
}

pub fn html_comment(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    htmlcomment { return Some(cursor); }
    * { return None; }
*/
}

pub fn html_processing_instruction(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    processinginstruction { return Some(cursor); }
    * { return None; }
*/
}

pub fn html_declaration(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    declaration { return Some(cursor); }
    * { return None; }
*/
}

pub fn html_cdata(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    cdata { return Some(cursor); }
    * { return None; }
*/
}

pub fn spacechars(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let len = s.len();
/*!re2c
    [ \t\v\f\r\n]+ { return Some(cursor); }
    * { return None; }
*/
}

pub fn link_title(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    ["] (escaped_char|[^"\xff])* ["]   { return Some(cursor); }
    ['] (escaped_char|[^'\xff])* ['] { return Some(cursor); }
    [(] (escaped_char|[^()\xff])* [)]  { return Some(cursor); }
    * { return None; }
*/
}

pub fn dangerous_url(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    'data:image/' ('png'|'gif'|'jpeg'|'webp') { return None; }
    'javascript:' | 'vbscript:' | 'file:' | 'data:' { return Some(cursor); }
    * { return None; }
*/
}

pub fn ipv6_url_start(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    'http' [s]? '://[' [0-9a-fA-F:]+ ('%25' [a-zA-Z0-9]+)? ']' { return Some(cursor); }
    * { return None; }
*/
}

pub fn ipv6_relaxed_url_start(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    [a-z]+ '://[' [0-9a-fA-F:]+ ('%25' [a-zA-Z0-9]+)? ']' { return Some(cursor); }
    * { return None; }
*/
}

/*!re2c

    table_spoiler = ['|']['|'];
    table_spacechar = [ \t\v\f];
    table_newline = ([\r][\n]|[\r\n]);

    table_delimiter = (table_spacechar*[:]?[-]+[:]?table_spacechar*);
    table_cell = (escaped_char|[^\xff|\r\n])+;
    table_cell_spoiler = (escaped_char|table_spoiler|[^\xff|\r\n])+;

*/

pub fn table_start(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    [|]? table_delimiter ([|] table_delimiter)* [|]? table_spacechar* (table_newline|[\xff]) {
        return Some(cursor);
    }
    * { return None; }
*/
}

pub fn table_cell(s: &str, spoiler: bool) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();

    // In fact, `table_cell` matches non-empty table cells only. The empty
    // string is also a valid table cell, but is handled by the default rule.
    // This approach prevents re2c's match-empty-string warning.
    if spoiler {
/*!re2c
    table_cell_spoiler { return Some(cursor); }
    * { return None; }
*/
    } else {
/*!re2c
    table_cell { return Some(cursor); }
    * { return None; }
*/
    }
}

pub fn table_cell_end(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let len = s.len();
/*!re2c
    [|] table_spacechar* { return Some(cursor); }
    * { return None; }
*/
}

pub fn table_row_end(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    table_spacechar* table_newline { return Some(cursor); }
    * { return None; }
*/
}

#[cfg(feature = "shortcodes")]
pub fn shortcode(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    [A-Za-z0-9+_-]+ [:] { return Some(cursor); }
    * { return None; }
*/
}

pub fn open_multiline_block_quote_fence(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let mut ctxmarker = 0;
    let len = s.len();
/*!re2c
    [>]{3,} / [ \t]*[\r\n\xff] { return Some(cursor); }
    * { return None; }
*/
}

pub fn close_multiline_block_quote_fence(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let mut ctxmarker = 0;
    let len = s.len();
/*!re2c
    [>]{3,} / [ \t]*[\r\n\xff] { return Some(cursor); }
    * { return None; }
*/
}

// Returns both the length of the match, and the tasklist item contents.
// It is not guaranteed to be one byte, or one "character" long; the caller must ascertain
// its fitness for purpose.
pub fn tasklist(s: &str) -> Option<(usize, &str)> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();

    let t1;
    let mut t2;
/*!stags:re2c format = 'let mut @@{tag} = 0;'; */

/*!local:re2c
    re2c:define:YYSTAGP = "@@{tag} = cursor;";
    re2c:define:YYSHIFTSTAG = "@@{tag} = (@@{tag} as isize + @@{shift}) as usize;";
    re2c:tags = 2;

    spacechar* [[] @t1 [^\xff\r\n\]]+ @t2 [\]] (spacechar | [\xff]) {
        if cursor == len + 1 {
            cursor -= 1;
        }
        return Some((cursor, &s[t1..t2]));
    }
    * { return None; }
*/
}

pub fn description_item_start(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let len = s.len();
/*!re2c
    [:~] ([ \t]+) { return Some(cursor); }
    * { return None; }
*/
}

#[cfg(feature = "phoenix_heex")]
pub fn phoenix_opening_tag(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    [<] phoenix_block_tag { return Some(cursor - 1); }
    * { return None; }
*/
}

#[cfg(feature = "phoenix_heex")]
pub fn phoenix_block_closing_tag(s: &str) -> Option<usize> {
    let mut cursor = 0;
    let mut marker = 0;
    let len = s.len();
/*!re2c
    [<][/] phoenix_block_tag [>] { return Some(cursor - 3); }
    * { return None; }
*/
}

#[cfg(feature = "phoenix_heex")]
pub fn phoenix_closing_tag(s: &str) -> Option<usize> {
    phoenix_block_closing_tag(s).map(|tag_len| tag_len + 3)
}

#[cfg(feature = "phoenix_heex")]
pub fn phoenix_directive(s: &str) -> Option<usize> {
    let bytes = s.as_bytes();
    let len = s.len();

    if s.len() < 4 || bytes[0] != b'<' || bytes[1] != b'%' {
        return None;
    }

    if bytes[2] == b'!' && s.len() >= 7 {
        let mut cursor = 0;
        let mut marker = 0;
/*!re2c
        '<%!--' ([^\xff-] | '-' [^\xff-] | '--' [^\xff%])* '--%>' { return Some(cursor); }
        * { return None; }
*/
    }

    if bytes[2] == b'#' {
        let mut cursor = 0;
        let mut marker = 0;
/*!re2c
        '<%#' ([^\xff%] | '%' [^\xff>])* '%>' { return Some(cursor); }
        * { return None; }
*/
    }

    let mut cursor = 2;
    while cursor + 1 < len {
        match bytes[cursor] {
            b'"' | b'\'' => {
                cursor = crate::strings::skip_quoted_string(s, cursor + 1, bytes[cursor]);
            }
            b'%' if bytes[cursor + 1] == b'>' => {
                return Some(cursor + 2);
            }
            _ => cursor += 1,
        }
    }

    None
}

#[cfg(feature = "phoenix_heex")]
pub fn phoenix_block_directive_start(s: &str) -> Option<usize> {
    let bytes = s.as_bytes();
    if bytes.len() >= 2 && bytes[0] == b'<' && bytes[1] == b'%' {
        Some(2)
    } else {
        None
    }
}

#[cfg(feature = "phoenix_heex")]
pub fn phoenix_block_expression_start(s: &str) -> Option<usize> {
    let bytes = s.as_bytes();
    if !bytes.is_empty() && bytes[0] == b'{' {
        Some(1)
    } else {
        None
    }
}

#[cfg(feature = "phoenix_heex")]
pub fn phoenix_block_directive_end(s: &str) -> bool {
    let bytes = s.as_bytes();
    let len = bytes.len();

    if len < 2 {
        return false;
    }

    let mut i = 0;
    while i < len {
        match bytes[i] {
            b'"' | b'\'' => {
                i = crate::strings::skip_quoted_string(s, i + 1, bytes[i]);
            }
            b'-' if i + 3 < len && bytes[i + 1] == b'-' && bytes[i + 2] == b'%' && bytes[i + 3] == b'>' => {
                return true;
            }
            b'%' if i + 1 < len && bytes[i + 1] == b'>' => {
                return true;
            }
            _ => i += 1,
        }
    }

    false
}

#[cfg(feature = "phoenix_heex")]
pub fn phoenix_block_comment_end(s: &str) -> bool {
    let bytes = s.as_bytes();
    let len = bytes.len();

    if len < 2 {
        return false;
    }

    let mut i = 0;
    while i < len {
        match bytes[i] {
            b'"' | b'\'' => {
                i = crate::strings::skip_quoted_string(s, i + 1, bytes[i]);
            }
            b'%' if i + 1 < len && bytes[i + 1] == b'>' => {
                return true;
            }
            _ => i += 1,
        }
    }

    false
}

#[cfg(feature = "phoenix_heex")]
pub fn phoenix_block_multiline_comment_end(s: &str) -> bool {
    let bytes = s.as_bytes();
    let len = bytes.len();

    if len < 4 {
        return false;
    }

    let mut i = 0;
    while i < len {
        match bytes[i] {
            b'"' | b'\'' => {
                i = crate::strings::skip_quoted_string(s, i + 1, bytes[i]);
            }
            b'-' if i + 3 < len && bytes[i + 1] == b'-' && bytes[i + 2] == b'%' && bytes[i + 3] == b'>' => {
                return true;
            }
            _ => i += 1,
        }
    }

    false
}

#[cfg(feature = "phoenix_heex")]
pub fn phoenix_block_expression_end(s: &str) -> bool {
    let bytes = s.as_bytes();
    let len = bytes.len();

    if len < 1 {
        return false;
    }

    let mut i = 0;
    while i < len {
        match bytes[i] {
            b'"' | b'\'' => {
                i = crate::strings::skip_quoted_string(s, i + 1, bytes[i]);
            }
            b'}' => {
                return true;
            }
            _ => i += 1,
        }
    }

    false
}
